package com.bac.rentmap.ad.flat.rating;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import org.springframework.beans.factory.annotation.Required;

import com.bac.rentmap.ad.flat.FlatAd;
import com.bac.rentmap.ad.flat.rating.item.RatingItemCalculator;

/**
 * Calculates {@link FlatAd} rating. The higher rating the better position in
 * search result.
 *
 * @author Andrew Osipenko
 */
public class FlatAdRatingCalculator {
    private static final Logger log = Logger
            .getLogger(FlatAdRatingCalculator.class.getName());
    private List<RatingItemCalculator> ratingItemCalculators;

    public long calculateRating(FlatAd flatAd) {
        log.finest("Calculating rating for " + flatAd);
        long res = 0;
        for (RatingItemCalculator ratingItemCalculator : ratingItemCalculators) {
            res += ratingItemCalculator.calculate(flatAd);
        }
        return res;
    }

    /**
    * Rating to display on UI (May be with stars).
    *
    * @param rating
    * @return greater or eq than 0, less or eq than 100
    */
    public List<Double> normalizeRatings(List<Long> ratings) {
        long maxPossibleRating = getMaxPossibleRating();
        long minPossibleRating = getMinPossibleRating();
        List<Double> res = new ArrayList<Double>(ratings.size());
        for (Long rating : ratings) {
            if (rating == null) {
                res.add(null);
            } else {
                double normalizedRating = normalizeRatingInternally(rating,
                        maxPossibleRating, minPossibleRating);
                res.add(normalizeRating100(normalizedRating));
            }
        }
        return res;
    }

    public double normalizeRating(long rating) {
        long maxPossibleRating = getMaxPossibleRating();
        long minPossibleRating = getMinPossibleRating();
        double res = normalizeRatingInternally(rating, maxPossibleRating,
                minPossibleRating);
        return normalizeRating100(res);
    }

    private double normalizeRating100(double rating) {
        double res = 100.0 * rating;
        if (res > 100)
            res = 100;
        else if (res < 0)
            res = 0;
        return res;
    }

    private double normalizeRatingInternally(long rating,
            long maxPossibleRating, long minPossibleRating) {
        return normalizeRatingInternally(rating, minPossibleRating,
                maxPossibleRating, minPossibleRating);
    }

    private double normalizeRatingInternally(long rating, long offset,
            long maxPossibleRating, long minPossibleRating) {
        return (double) (rating - offset)
                / (maxPossibleRating - minPossibleRating);
    }

    public NormalizedRatingExplanation explainNormalizedRating(FlatAd flatAd) {
        long maxPossibleRating = getMaxPossibleRating();
        long minPossibleRating = getMinPossibleRating();
        List<NormalizedRatingItemExplanation> itemExplanations = new ArrayList<NormalizedRatingItemExplanation>(
                ratingItemCalculators.size());
        for (RatingItemCalculator ratingItemCalculator : ratingItemCalculators) {
            long itemRating = ratingItemCalculator.calculate(flatAd);
            double normalizedItemRating = normalizeRatingInternally(itemRating,
                    ratingItemCalculator.getMinPossibleRating(),
                    maxPossibleRating, minPossibleRating);
            normalizedItemRating = normalizeRating100(normalizedItemRating);
            itemExplanations.add(new NormalizedRatingItemExplanation(
                    normalizedItemRating, ratingItemCalculator.getClass()));
        }
        double normalizedRating = normalizeRatingInternally(flatAd.getRating(),
                maxPossibleRating, minPossibleRating);
        return new NormalizedRatingExplanation(
                normalizeRating100(normalizedRating), itemExplanations);
    }

    private long getMinPossibleRating() {
        long res = 0;
        for (RatingItemCalculator ratingItemCalculator : ratingItemCalculators) {
            res += ratingItemCalculator.getMinPossibleRating();
        }
        return res;
    }

    private long getMaxPossibleRating() {
        long res = 0;
        for (RatingItemCalculator ratingItemCalculator : ratingItemCalculators) {
            res += ratingItemCalculator.getMaxPossibleRating();
        }
        return res;
    }

    /**
    * Calculates flat ad rating considering distance to the point selected by
    * user. 7 days old 0km distance ad has the same distance rating as todays
    * 7km distance ad.
    *
    * @param flatAdRating
    *            calculated by {@link #calculateRating(FlatAd)}
    * @param distance
    *            in kilometers
    * @return
    */
    public long calculateDistanceRating(long flatAdRating, double distance) {
        return (long) (flatAdRating - 700 * distance);
    }

    @Required
    public void setRatingItemCalculators(
            List<RatingItemCalculator> ratingItemCalculators) {
        this.ratingItemCalculators = ratingItemCalculators;
    }
}
